// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    public partial class LogicalExpressionType : BooleanExpressionType
    {
        public static LogicalOperatorsEnum LogicalOperatorsEnumFromString(string value)
        {
            if (string.Compare(value, "AND", true) == 0)
                return LogicalOperatorsEnum.AND;
            else if (string.Compare(value, "NOT", true) == 0)
                return LogicalOperatorsEnum.NOT;
            else if (string.Compare(value, "OR", true) == 0)
                return LogicalOperatorsEnum.OR;
            else
                throw new ArgumentException(string.Format("{0} is not a valid LogicalOperatorEnum value", value));
        }

        LogicalOperatorsEnum m_ExpressionOperator;

        public new LogicalOperatorsEnum ExpressionOperator
        {
            get { return m_ExpressionOperator; }
            set { m_ExpressionOperator = value; }
        }

        public LogicalExpressionType(LogicalOperatorsEnum expressionOperator, IBooleanValue left)
        {
            m_ExpressionOperator = expressionOperator;
            if (left is BooleanExpressionType)
                Operands.Add((BooleanExpressionType)left);
            else
                Operands.Add((BooleanAtomType)left);
        }

        public LogicalExpressionType(LogicalOperatorsEnum expressionOperator, IBooleanValue left, IBooleanValue right)
        {
            m_ExpressionOperator = expressionOperator;
            if (left is BooleanExpressionType)
                Operands.Add((BooleanExpressionType)left);
            else
                Operands.Add((BooleanAtomType)left);
            if (right != null)
            {
                if (right is BooleanExpressionType)
                    Operands.Add((BooleanExpressionType)right);
                else
                    Operands.Add((BooleanAtomType)right);
            }
        }

        public LogicalExpressionType(IBooleanValue left, LogicalOperatorsEnum expressionOperator, IBooleanValue right)
        {
            m_ExpressionOperator = expressionOperator;
            if (left is BooleanExpressionType)
                Operands.Add((BooleanExpressionType)left);
            else
                Operands.Add((BooleanAtomType)left);
            if (right is BooleanExpressionType)
                Operands.Add((BooleanExpressionType)right);
            else
                Operands.Add((BooleanAtomType)right);
        }

        public LogicalExpressionType(LogicalOperatorsEnum expressionOperator, List<IBooleanValue> expressionOperands)
        {
            m_ExpressionOperator = expressionOperator;
            foreach (IBooleanValue value in expressionOperands)
                if (value is BooleanExpressionType)
                    Operands.Add((BooleanExpressionType)value);
                else
                    Operands.Add((BooleanAtomType)value);
        }

        public LogicalExpressionType(XElement element)
            : base()
        {
            // NB this by-passes the ExpressionType(XElement element) constructor
            // everything up to "here:" should replicate the code in that constructor
            var query = from e in element.Elements()
                        where e.Name.NamespaceName == CIValueType.RuleXmlNS
                        select e;
            foreach (XElement operandElement in query)
                Operands.Add(CIValueType.Initialize(operandElement));
            // here:
            XAttribute operatorAtt = element.Attribute("Operator");
            if (operatorAtt != null)
                ExpressionOperator = LogicalOperatorsEnumFromString(operatorAtt.Value);
        }

        public override void XmlFor(XmlWriter xwrtr, bool writeNamespace)
        {
            xwrtr.WriteStartElement("LogicalExpression");
            if (writeNamespace)
                xwrtr.WriteAttributeString("xmlns", RuleXmlNS);
            writeNamespace = false;
            xwrtr.WriteAttributeString("Operator", ExpressionOperator.ToString());
            foreach (CIValueType val in Operands)
                val.XmlFor(xwrtr, writeNamespace);
            xwrtr.WriteEndElement();
        }

        public override bool Check(IDataPacketType data)
        {
            bool result = true;

            LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
            {
                if (status == MajorEnum.Error)
                    result = false;
                return data.Logger(status, minorStatus, message, paras);
            };

            switch (ExpressionOperator)
            {
                case LogicalOperatorsEnum.AND:
                case LogicalOperatorsEnum.OR:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A logical expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A logical expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!(Operands[0] is IBooleanValue && Operands[1] is IBooleanValue))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the {0} operator must be boolean expressions or boolean value: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case LogicalOperatorsEnum.NOT:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A logical expression using the {0} operator cannot have more than one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A logical expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!(Operands[0] is IBooleanValue))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the {0} operator must be boolean expressions or boolean value: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                default:
                    {
                        localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                            "{0} is not a recognized logical operator: {1}", 
                            new object[] { ExpressionOperator.ToString(), this.ToString() });
                        break;
                    }
            }
            return result;
        }

        public override object Evaluate(IDataPacketType data)
        {
            if (data != null && data.PacketStats != null)
                data.PacketStats.LogicalOperatorsCount[(int)ExpressionOperator] += 1;
            switch (ExpressionOperator)
            {
                case LogicalOperatorsEnum.AND:
                    return (bool)Operands[0].Evaluate(data) && (bool)Operands[1].Evaluate(data);
                case LogicalOperatorsEnum.OR:
                    return (bool)Operands[0].Evaluate(data) || (bool)Operands[1].Evaluate(data);
                case LogicalOperatorsEnum.NOT:
                    return !(bool)Operands[0].Evaluate(data);
                default:
                    throw new ApplicationException("unexpected boolean operator type encountered: working on " + ToString());
            }
        }

        public override List<string> ComparesTo(string name)
        {
            List<string> names = new List<string>();
            if (ExpressionOperator == LogicalOperatorsEnum.AND)
            {
                foreach (IBooleanValue value in Operands)
                    names.AddRange(((BooleanExpressionType)value).ComparesTo(name));
            }
            return names;
        }

        public override string ToString(ExpressionFormatEnum format)
        {
            switch (format)
            {
                case ExpressionFormatEnum.text:
                    return ToString();
                case ExpressionFormatEnum.sql:
                case ExpressionFormatEnum.xpath:
                    return ToSqlString();
                case ExpressionFormatEnum.javascript:
                    return ToJavaScriptString();
                default:
                    throw new InvalidExpressionException("Unhandled expression format:" + format);
            }
        }

        public override string ToString()
        {
            string leftOp = Operands.Count > 0 ? Operands[0].ToString() : "InvalidOPerand";
            string rightOp = Operands.Count > 1 ? Operands[1].ToString() : "InvalidOPerand";
            switch (ExpressionOperator)
            {
                case LogicalOperatorsEnum.AND:
                    return "(" + leftOp + ") AND (" + rightOp + ")";
                case LogicalOperatorsEnum.OR:
                    return "(" + leftOp + ") OR (" + rightOp + ")";
                case LogicalOperatorsEnum.NOT:
                    return "NOT(" + leftOp + ")";
                default:
                    return "(" + leftOp + ")InvalidOperand(" + rightOp + ")";
            }
        }

        private string ToSqlString()
        {
            string leftOp = Operands.Count > 0 ? GetSqlOpndString(Operands[0]) : "InvalidOPerand";
            string rightOp = Operands.Count > 1 ? GetSqlOpndString(Operands[1]) : "InvalidOPerand";
            switch (ExpressionOperator)
            {
                case LogicalOperatorsEnum.AND:
                    return "(" + leftOp + ") AND (" + rightOp + ")";
                case LogicalOperatorsEnum.OR:
                    return "(" + leftOp + ") OR (" + rightOp + ")";
                case LogicalOperatorsEnum.NOT:
                    return "NOT(" + leftOp + ")";
                default:
                    return "(" + leftOp + ")InvalidOperand(" + rightOp + ")";
            }
        }

        private string ToJavaScriptString()
        {
            string leftOp = Operands[0].ToString(ExpressionFormatEnum.javascript);
            string rightOp = Operands.Count > 1 ? Operands[1].ToString(ExpressionFormatEnum.javascript) : "InvalidOPerand";
            switch (ExpressionOperator)
            {
                case LogicalOperatorsEnum.AND:
                    if (Operands.Count != 2)
                        throw new InvalidExpressionException(Operands.Count + "operands for 'AND' LogicalExpressionType.");
                    return "(" + leftOp + ")&&(" + rightOp + ")";
                case LogicalOperatorsEnum.OR:
                    if (Operands.Count != 2)
                        throw new InvalidExpressionException(Operands.Count + "operands for 'OR' LogicalExpressionType.");
                    return "(" + leftOp + ")||(" + rightOp + ")";
                case LogicalOperatorsEnum.NOT:
                    if (Operands.Count != 1)
                        throw new InvalidExpressionException(Operands.Count + "operands for 'NOT' LogicalExpressionType.");
                    return "!(" + leftOp + ")";
                default:
                    throw new InvalidExpressionException("Unhandled operator: " + ExpressionOperator);
            }
        }
    }
}
